#!/bin/bash
# Based on https://adamkuj.net/blog/2016/05/18/pro-tip-port-mirroring-in-linux/

# destination bridge name
BRIDGE_IF="mirror0"

# dummy interface name
DUMMY_IF="dummy0"

# module name
MODNAME="${0##*/}"

# utilities
AWK="/usr/bin/awk"
BRCTL="/usr/sbin/brctl"
GREP="/bin/grep"
IP="/sbin/ip"
SYSLOG="/usr/bin/logger -t $MODNAME"
TC="/sbin/tc"

case "$1" in
  start)
        # get parameters
        SRC_IF=$2
        DST_IF=$3

        # check prerequisities
        $IP link ls dev $DUMMY_IF >/dev/null 2>&1
        if [ $? -ne 0 ]; then
          $SYSLOG "missing $DUMMY_IF interface, incompatible firmware, update needed"
          exit 1
        fi
        if [ "$SRC_IF" = "$DST_IF" ]; then
          $SYSLOG "source and destination interfaces must be different"
          exit 1
        fi
        $IP link ls dev $SRC_IF >/dev/null 2>&1
        if [ $? -ne 0 ]; then
          $SYSLOG "source interface '$SRC_IF' doesn't exist"
          exit 1
        fi
        $IP link ls dev $DST_IF >/dev/null 2>&1
        if [ $? -ne 0 ]; then
          $SYSLOG "destination interface '$DST_IF' doesn't exist"
          exit 1
        fi
        $IP link ls dev $DST_IF | $GREP -q "state UP"
        if [ $? -eq 0 ]; then
          $SYSLOG "destination interface '$DST_IF' is busy (up)"
          exit 1
        fi
        IP_NR=`$IP addr ls dev $DST_IF | $AWK "BEGIN {ip_nr = 0;} /inet/ {++ip_nr;} END {print ip_nr}"`
        if [ $IP_NR -ne 0 ]; then
          $SYSLOG "destination interface '$DST_IF' is busy (address assigned)"
          exit 1
        fi

        # create destination bridge
        $IP link ls dev $BRIDGE_IF >/dev/null 2>&1
        if [ $? -eq 0 ]; then
          $SYSLOG "allready running, $BRIDGE_IF exists"
          exit 1
        fi
        $BRCTL addbr $BRIDGE_IF
        $BRCTL addif $BRIDGE_IF $DST_IF
        $BRCTL addif $BRIDGE_IF $DUMMY_IF
        $IP link set dev $BRIDGE_IF up    

        # start destination interface
        $IP link set dev $DST_IF up

        # start dummy interface
        $IP link set dev $DUMMY_IF up

        # tc filter + action
        TC_FILTER_ACTION="protocol all u32 match u8 0 0 action mirred egress mirror dev $BRIDGE_IF"

        # add mirroring of ingress traffic
        $TC qdisc add dev $SRC_IF ingress
        if [ $? -ne 0 ]; then
          $SYSLOG "missing tc qdisc ingress support, incompatible firmware, update needed"
          $0 stop $SRC_IF $DST_IF
          exit 1
        fi
        $TC filter add dev $SRC_IF parent ffff: $TC_FILTER_ACTION
        if [ $? -ne 0 ]; then
          $SYSLOG "missing tc filter action mirred support, incompatible firmware, update needed"
          $0 stop $SRC_IF $DST_IF
          exit 1
        fi

        # add mirroring of egress traffic
        $TC qdisc add dev $SRC_IF handle 1: root prio
        $TC filter add dev $SRC_IF parent 1: $TC_FILTER_ACTION
        ;;

  stop)
        # check if destination bridge exists
        $IP link ls dev $BRIDGE_IF >/dev/null 2>&1 || exit 1

        # get parameters
        SRC_IF=$2
        DST_IF=$3

        # autodetect source interface
        # looking for ethX interface with qdisc prio and ingress (both)
        if [ -z "$SRC_IF" ]; then
          IF_LIST=`$IP link | $AWK "/eth[0-9].*qdisc prio/ {gsub(\\":\\", \\"\\", \\$2); print \\$2}"`
          for IF in $IF_LIST; do
            $TC qdisc show dev $IF | $GREP -q "qdisc ingress"
            if [ $? -eq 0 ]; then
              SRC_IF=$IF
              break
            fi
          done
          if [ -z "$SRC_IF" ]; then
            $SYSLOG "cannot autodetect source interface, aborting"
            exit 1
          fi
        fi

        # autodetect destination interface
        # looking for ethX interface inside $BRIDGE_IF
        if [ -z "$DST_IF" ]; then
          DST_IF=`$BRCTL show | $AWK "/$BRIDGE_IF.*eth/ {gsub(\\".*eth\\",\\"eth\\"); print}"`
          if [ -z "$DST_IF" ]; then
            $SYSLOG "cannot autodetect destination interface, aborting"
            exit 1
          fi
        fi

        # remove mirroring of egress traffic
        $TC qdisc del dev $SRC_IF root

        # remove mirroring of ingress traffic
        $TC qdisc del dev $SRC_IF ingress

        # stop dummy interface
        $IP link set dev $DUMMY_IF down

        # stop destination interface
        $IP link set dev $DST_IF down

        # remove destination bridge
        $IP link set dev $BRIDGE_IF down
        $BRCTL delif $BRIDGE_IF $DUMMY_IF
        $BRCTL delif $BRIDGE_IF $DST_IF
        $BRCTL delbr $BRIDGE_IF
        ;;

  status)
        # get parameter
        [ "$2" = "-h" ] && HTML_OUTPUT=1

        # check if destination bridge exists
        $IP link ls dev $BRIDGE_IF >/dev/null 2>&1
        RETVAL=$?
        if [ $RETVAL -eq 0 ]; then
          STATUS="running"
          COLOR="green"
        else
          STATUS="stopped"
          COLOR="red"
        fi

        # decorate status if HTML output is required
        if [ -n "$HTML_OUTPUT" ]; then
          STATUS="<span style=\"color:$COLOR\">$STATUS</span>"
        fi

        # print status
        echo "Module $MODNAME is $STATUS"
        exit $RETVAL
        ;;

  *)
	echo "Usage: $0 {start <src_if> <dst_if>|stop|status}"
	exit 1
esac
